/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package TimeRecording.TimeClockServer;

import TimeRecording.TimeClockServer.messages.ClockMessage;

/**
 * This class is responsible for collating incoming bytes and forming them into
 * complete messages.  The buffer is cyclical in nature, storing data in a fixed
 * length array.
 *
 * All methods on this class are synchronised to ensure it is thread-safe.
 *
 * @author dpatchett
 */
public class RxBuffer {

    // A standard message header; all messages must start with this sequence
    // whether coming from or going to the clock terminal.
    private byte[] HEADER = new byte[] { 92, 65, 33,29 };

    // A delimter for the end of a message - note that this is probably
    // wrong as it's currently pointing to a record separator
    private byte TERMINATOR = 30;

    // A 4k buffer ought to be big enough
    private final static int MAX_SIZE = 0x1000;

    // Diagnostic to throw if we do fill the buffer up
    private final static String BUFFER_FULL_ERROR = "Response Buffer Full";

    // Variables for managing our internal cyclical buffer
    private byte[] buffer = new byte[MAX_SIZE];
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    // A simple semaphore indicating that an acknowledgement has been received
    private boolean rxAck = false;

    /**
     * This method will add the specified bytes to the internal buffer.  A
     * boolean is returned to the caller to indicate that at least one complete
     * message is waiting in the buffer.  Such a message could be a transaction,
     * a response or an acknowledgement.
     *
     * @param bytes an array of bytes
     * @param len the number of bytes to be read from the array
     * @return a boolean indicating a message is waiting to be read
     */
    public synchronized boolean addBytes(byte[] bytes, int len) {

        for (int index = 0; index < len; index++) {
            if (size >= MAX_SIZE) {
                throw new ArrayIndexOutOfBoundsException(BUFFER_FULL_ERROR);
            }
            buffer[tail] = bytes[index];
            tail = increment(tail);
            size++;
        }

        // Remove any unwanted "noise" at the head of the buffer
        removeBadBytes();

        // Is a message read to be read?
        return messageReady();
    }

    public synchronized boolean messageReady() {

        // Due to the cleansing of unmatched "crap" at the head of the queue
        // and the fact that the message is at least as big as the header then
        // we'll know instantly if we've got a valid header.

        if (buffer.length < HEADER.length) {
            return false;
        }

        // We just need to check for a valid message terminator
        int offset = head;
        for (int index = 0; index < size; index++) {

            if (ClockServer.USING_EOT_MARKER) {
                if (buffer[offset] == ClockMessage.EOT) {
                    return true;
                }
                
            } else if (buffer[offset] == TERMINATOR) {
                return true;
            }
            
            offset = increment(offset);
        }
        return false;
    }

    public synchronized ClockMessage getMessage() {

        // Pass 1 - work out the size of the response (including terminator)

        int responseSize = 0;
        int offset = head;
        for (int index = 0; index < size; index++) {
            responseSize++;
            if (ClockServer.USING_EOT_MARKER) {
                if (buffer[offset] == ClockMessage.EOT) {
                    break;
                }
            } else if (buffer[offset] == TERMINATOR) {
                break;
            }
            offset = increment(offset);
        }

        offset = head;
        byte[] response = new byte[responseSize];

        for (int index = 0; index < responseSize; index++) {
            response[index] = buffer[offset];
            offset = increment(offset);
            size--;
        }

        head = offset;
        return new ClockMessage(response);
    }

    /**
     * This method is responsible for removing any rogue bytes from the head of
     * the message buffer.  It is not expected that it will ever remove anything
     * but is merely there as a safeguard.
     *
     * The method simply works by checking that buffer contains a message header
     * or, if the buffer is particularly low, that what is in the matches the
     * start of a message header.
     */
    private synchronized void removeBadBytes() {

        int bufferOffset = head;
        int headerOffset = 0;

        for (int index = 0; index < size; index++) {

            // Check to see if we're matcing bits of the header

            if (buffer[bufferOffset] == HEADER[headerOffset]) {

                headerOffset++;

                // If we've matched a full header then bail out as we've
                // already removed any crap at the head of the buffer

                if (headerOffset == HEADER.length) {
                    break;
                }

            } else {

                // If we've encountered a byte that's not part of a
                // legitimate "full" then skip everything up to now

                head = bufferOffset;
                size -= (headerOffset + 1);
                headerOffset = 0;
            }

            bufferOffset = increment(bufferOffset);
        }
    }

    /**
     * A simple method to safely increment an offset into the buffer, wrapping
     * it back to the start of the buffer where necessary.
     *
     * @param offset an offset into the buffer
     * @return an incremented (possibly wrapped) offset into the buffer
     */
    private synchronized int increment(int offset) {
        return (offset + 1) % MAX_SIZE;
    }

    /**
     * Sets a simple semaphore to indicate that an acknowledgement has been
     * received.
     */
    public synchronized void recordAckReceived() {
        rxAck = true;
    }

    /**
     * Simply tests the "acknowledgement" semaphore and returns its value.  The
     * semaphore is cleared as part of the method.
     *
     * @return a boolean indicating that an acknowledgement has been received
     */
    public synchronized boolean testAckReceived() {
        boolean result = rxAck;
        rxAck = false;
        return result;
    }
}
